(function(doc, win) {

	var FPS = 30,
		Animation_End,
		borderSize,
		canvas,
		blocks = [],
		score = 0,
		lines = 0,
		level = 1,
		isStart,
		dropLoopHandle,
		hasInited,
		currentShape,
		blockSize,
		suspended,
		inQuickDrop
		SHAPES = [
			[
				[
					[1, 1],
					[1, 1]
				]
			],
			[
				[
					[0, 0, 0, 0],
					[1, 1, 1, 1],
					[0, 0, 0, 0],
					[0, 0, 0, 0]
				],
				[
					[0, 0, 1, 0],
					[0, 0, 1, 0],
					[0, 0, 1, 0],
					[0, 0, 1, 0]
				]
			],
			[
				[
					[0, 0, 0],
					[1, 1, 1],
					[1, 0, 0]
				],
				[
					[1, 1, 0],
					[0, 1, 0],
					[0, 1, 0]
				],
				[
					[0, 0, 0],
					[0, 0, 1],
					[1, 1, 1]
				],
				[
					[0, 1, 0],
					[0, 1, 0],
					[0, 1, 1]
				]
			],
			[
				[
					[0, 0, 0],
					[1, 1, 1],
					[0, 0, 1]
				],
				[
					[0, 1, 0],
					[0, 1, 0],
					[1, 1, 0]
				],
				[
					[0, 0, 0],
					[1, 0, 0],
					[1, 1, 1]
				],
				[
					[0, 1, 1],
					[0, 1, 0],
					[0, 1, 0]
				]
			],
			[
				[
					[0, 0, 0],
					[1, 1, 1],
					[0, 1, 0]
				],
				[
					[0, 1, 0],
					[1, 1, 0],
					[0, 1, 0]
				],
				[
					[0, 0, 0],
					[0, 1, 0],
					[1, 1, 1]
				],
				[
					[0, 1, 0],
					[0, 1, 1],
					[0, 1, 0]
				]
			],
			[
				[
					[0, 0, 0],
					[0, 1, 1],
					[1, 1, 0]
				],
				[
					[1, 0, 0],
					[1, 1, 0],
					[0, 1, 0]
				]
			],
			[
				[
					[0, 0, 0],
					[1, 1, 0],
					[0, 1, 1]
				],
				[
					[0, 0, 1],
					[0, 1, 1],
					[0, 1, 0]
				]
			]
		],
		SHIFTS = [{
			x: 0,
			y: 0
		}, {
			x: 1,
			y: 0
		}, {
			x: 2,
			y: 0
		}, {
			x: -1,
			y: 0
		}, {
			x: -2,
			y: 0
		}, {
			x: 0,
			y: -1
		}];

	function gameStart() {
		canvas = doc.getElementById('canvas');
		if(canvas.childElementCount > 0) {
			canvas.innerHTML = '';
		}
		var wrap = doc.getElementById('wrap'),
			states = doc.getElementById('states'),
			canvasWidth = ~~(canvas.clientWidth),
			canvasHeight = ~~(wrap.clientHeight - states.clientHeight);

		blockSize = ~~(canvasWidth / (10 + 2 / 3));
		borderSize = ~~(blockSize / 30);
		borderSize = borderSize <= 0 ? 1 : borderSize;
		var hNum = ~~(canvasHeight / blockSize);
		blocks = [hNum];
		canvas.style.height = canvasHeight + 'px';
		canvas.style.paddingTop = ~~(canvasHeight - blockSize * hNum) / 2 + 'px';
		canvas.style.paddingLeft = ~~(canvasWidth - blockSize * 10) / 2 + 'px';

		var fragment = document.createDocumentFragment();
		for(var i = 0; i < hNum; i++) {
			blocks[i] = [];
			fullRows(blocks[i], fragment);
		}
		canvas.appendChild(fragment);
		dropLoop();
		init();
	};
	
	function gameRestart() {
		clearTimeout(dropLoopHandle);
		if(currentShape){
			currentShape.dispose();
			currentShape = null;
		}
		gameStart();
	}
	
	function gameOver() {
		clearTimeout(dropLoopHandle);
		if(currentShape){
			currentShape.dispose();
			currentShape = null;
		}
	}

	function checkGameOver() {
		for(var i in blocks[0]) {
			if(blocks[0][i].color > -1) {
				gameOver();
				return true;
			}
		}
		return false;
	}
	
	function dropLoop(toBottom) {
		if(!suspended) {
			if(!currentShape) {
				currentShape = createShape();
				clearTimeout(dropLoopHandle);
				dropLoopHandle = setTimeout(dropLoop, getDropInterval());
			} else {
				var result = toBottom || currentShape.move(0, 1);
				if(toBottom || result.toBottom) {
					clearRows(function(clearRowNum){
						if(clearRowNum<=0) {
							currentShape.flash(function(){
								if(checkGameOver()) {
									return;
								}
								createCurrentShape();
								clearTimeout(dropLoopHandle);
								dropLoopHandle = setTimeout(dropLoop, getDropInterval());
							});
							return;
						}
						if(changeScoreAndUpLevel(clearRowNum)){
							return;
						}
						createCurrentShape();
						clearTimeout(dropLoopHandle);
						dropLoopHandle = setTimeout(dropLoop, getDropInterval());
					});
				}else{
					clearTimeout(dropLoopHandle);
					dropLoopHandle = setTimeout(dropLoop, getDropInterval());
				}
			}
		}
	};
	
	function createCurrentShape(){
		if(currentShape){
			currentShape.dispose()
			currentShape = null;
		}
		currentShape = createShape();
		currentShape.drew();
	}
	
	function getDropInterval(){
		return 800 * Math.pow(0.9, level - 1);
	}
	
	function changeScoreAndUpLevel(rowNum){
		if(rowNum === 1){
			score +=40;
		}else if(rowNum === 2){
			score +=100;
		}else if(rowNum === 3){
			score +=300;
		}else if(rowNum === 4){
			score +=1200;
		}
		lines += rowNum;
		var newLevel = ~~(lines/30);
		
		var scoreEl = document.getElementById('score');
		var linesEl = document.getElementById('lines');
		var levelEl = document.getElementById('level');
		scoreEl.innerText=score;
		linesEl.innerText=lines;
		if(newLevel > level){
			levelEl.innerText=level;
			gameRestart();
			return true;
		}
		return false;
	}
	
	function fullRows(blockRow, fragment, insertFirst){
		var rowDiv = document.createElement('div');
		rowDiv.className='row';
		for(var y = 0; y < 10; y++) {
			var el = document.createElement('div');
			blockRow[y] = {
				el: el,
				color: -1
			};
			el.className = 'block';
			el.style.width = el.style.height = blockSize + 'px';
			el.style.borderWidth = borderSize;
			var innerBlock = document.createElement('div');
			innerBlock.className = 'innerBlock';
			el.appendChild(innerBlock)
			rowDiv.appendChild(el);
		}
		if(insertFirst){
			fragment.insertBefore(rowDiv, fragment.firstChild);
		}else{
			fragment.appendChild(rowDiv);
		}
	}

	function clearRows(callBack) {
		if(currentShape){
			var includeRows = [],
				excludeRows = [],
				full;
			currentShape.positions.forEach(function(position){
				if(position.y<0 || includeRows.indexOf(position.y)>=0 || excludeRows.indexOf(position.y)>=0){
					return;
				}
				full = blocks[position.y].every(function(block){
					return block.color>=0;
				});
				if(full){
					includeRows.push(position.y);
				}else{
					excludeRows.push(position.y);
				}
			});
			if(includeRows.length>0){
				if(currentShape){
					currentShape.dispose()
					currentShape = null;
				}
				var rowNum, newBlocks = [];
				console.log(includeRows);
				for(var i=0;i<blocks.length;i++){
					if(includeRows.indexOf(i)<0){
						newBlocks.push(blocks[i]);
						console.log(i);
					}
				}
				
				for(var i=0;i<includeRows.length;i++){
					rowNum = includeRows[i];
					var rowEl = document.querySelector('.row:nth-child('+ (rowNum+1) +')');
					includeRows[i] = rowEl;
					if(i===includeRows.length-1){
						rowEl.addEventListener(Animation_End, function(){
							var fragment = document.createDocumentFragment();
							includeRows.forEach(function(rowEl){
								canvas.removeChild(rowEl);
								var blockRow = [];
								newBlocks.unshift(blockRow);
								fullRows(blockRow, fragment, true);
							});
							canvas.insertBefore(fragment,canvas.firstChild);
							if(callBack){
								setTimeout(function(){
									callBack(includeRows.length);
								}, 0);
							}
							blocks = newBlocks;
						});
					}
					rowEl.classList.toggle('clear');
				}
				return;
			}
		}
		callBack(0);
	}

	function createShape(basePosition) {
		var shape = new Shape(basePosition);
		return shape.isCreated ? shape : null;
	}

	function init() {
		if(hasInited) {
			return;
		}
		hasInited = true;
		
		Animation_End = (canvas.style['animationend'])?'animationend':'webkitAnimationEnd';
		var touchData = {},
			moveHandle,
			touchQuickDropHandle;
		doc.addEventListener('touchstart', function(e) {
			if(e.touches && e.touches.length > 0) {
				var touch = e.touches[0];
				touchData.startX = touchData.lastMoveX = touchData.lastX = touch.pageX;
				touchData.startY = touchData.lastMoveY = touchData.lastY = touch.pageY;
				touchData.deltaX = touchData.deltaY = touchData.downY = 0;
				touchData.startTime = new Date().getTime();
			}
			event.preventDefault();
		});
		doc.addEventListener('touchmove', function(e) {
			if(e.touches && e.touches.length > 0) {
				var touch = e.touches[0];
				touchData.nowX = touch.pageX;
				touchData.nowY = touch.pageY;

				touchData.deltaX += Math.abs(touchData.nowX - touchData.startX);
				touchData.deltaY += Math.abs(touchData.nowY - touchData.startY);

				var movedX = touchData.nowX - touchData.lastMoveX;
				var movedY = touchData.nowY - touchData.lastMoveY;

				var xNeedMove = Math.abs(movedX) >= blockSize;
				var yNeedMove = movedY >= blockSize;

				var relativeX = touchData.nowX - touchData.lastX;
				var relativeY = touchData.nowY - touchData.lastY;
				if(relativeY > 0 && (relativeX === 0 || Math.abs(relativeY / relativeX) >= 1.5)) {
					if(touchData.downY === 0) {
						touchData.downTime = new Date().getTime();
					}
					touchData.downY += relativeY;
				} else {
					touchData.downY = 0;
					touchData.downTime = 0;
				}
				touchData.lastX = touchData.nowX;
				touchData.lastY = touchData.nowY;
				touchData.lastMoveX = xNeedMove ? touchData.nowX : touchData.lastMoveX;
				touchData.lastMoveY = (yNeedMove || movedY < 0) ? touchData.nowY : touchData.lastMoveY;
				if(xNeedMove || yNeedMove) {
					moveHandle = setTimeout(function() {
						touchMove(xNeedMove ? (movedX > 0 ? 1 : -1) : 0, ~~yNeedMove);
					}, 0);
				}
			}
		});
		doc.addEventListener('touchend', function(e) {
			if(touchData.deltaX > 30 || touchData.deltaY > 30) {
				var time = new Date().getTime(),
					deltaX = Math.abs(touchData.nowX - touchData.startX),
					deltaY = Math.abs(touchData.nowY - touchData.startY);
				if(time - touchData.startTime < 200 && deltaY >= blockSize * 1.5 && deltaX < deltaY ||
					touchData.downY >= blockSize * 1.5 && time - touchData.downTime < 200) {
					touchQuickDropHandle = setTimeout(touchQuickDrop, 0);
				}
			} else {
				//e.target.dispatchEvent(createEvent('tap'));
				setTimeout(touchRotate, 0);
			}
			touchData = {};
		});
		doc.addEventListener('touchcancel', function(e) {
			touchData = {};
			clearTimeout(moveHandle);
			clearTimeout(touchQuickDropHandle);
			moveHandle = null;
		});
	}

	function touchMove(changeX, changeY) {
		if(!suspended && currentShape) {
			currentShape.move(changeX, changeY);
		}
	}

	function touchRotate() {
		if(!suspended && currentShape) {
			currentShape.rotate();
		}
	}

	function touchQuickDrop() {
		if(!suspended && currentShape) {
			clearTimeout(dropLoopHandle);
			var result = currentShape.move(0, 1);
			if(result.toBottom) {
//				toggleClass(canvas, 'shake')
//				setTimeout(function() {
//					toggleClass(canvas, 'shake');
//				}, 200);
				dropLoop(true);
				return;
			}
			currentShape.drew();
			dropLoopHandle = setTimeout(touchQuickDrop, 10);
		}
	}

	function createEvent(type) {
		var event = doc.createEvent('HTMLEvents');
		event.initEvent(type);
		return event;
	}

	function Shape(basePosition) {
		var _Positions,
			type = ~~(Math.random() * 7),
			color = type;

		var matrixType = 0;

		if(~~(Math.random() * 2)) {
			matrixType = rotateMatrix(type, matrixType);
		}
		var matrix = SHAPES[type][matrixType];
		if(!basePosition) {
			var end;
			for(var i = 0; i < matrix.length; i++) {
				var rat = matrix[i].some(function(value) {
					return value !== 0
				});
				if(rat) {
					end = i;
				} else if(end) {
					break;
				}
			}
			basePosition = {
				x: 5 - ~~(matrix.length / 2),
				y: -end
			};
		} else if(matrix.length === 3) {
			basePosition = {
				x: basePosition.x + 1,
				y: basePosition.y + 1
			};
		}
		var positions = matrix2Positions(basePosition, type, matrixType);
		var canCreate = true;
		for(var i in positions) {
			var row = positions[i].y,
				col = positions[i].x;
			if(row >= 0) {
				var block = blocks[row][col];
				if(block && block.color > -1) {
					canCreate = false;
					break;
				}
			}
		}
		if(!canCreate) {
			this.isCreated = false;
			return;
		}
		this.type = type;
		this.color = color;
		this.matrixType = matrixType;
		this.basePosition = basePosition;
		this._Positions = positions;
		this.isCreated = true;
	}

	Shape.prototype = {
		constructor: Shape,
		drew: function() {
			var positions = this.positions;
			var oldPositions = this.oldPositions;
			if(oldPositions) {
				oldPositions.forEach(function(oldPosition) {
					var x = oldPosition.x,
						y = oldPosition.y,
						block = blocks[y][x];
					if(!block) {
						return;
					}
					block.inCurrentShape = false;
					if(block.color>=0){
						block.el.classList.toggle('block-'+block.color);
						block.color = -1;
					}
				});
			}
			var color = this.color;
			positions.forEach(function(position) {
				var y = position.y,
					x = position.x,
					block = y < 0 ? null : blocks[y][x];
				if(!block) {
					return;
				}
				block.inCurrentShape = true;
				var el = block.el,
					bColor = block.color;

				if(bColor !== color) {
					block.color = color;
					el.classList.toggle('block-'+color);
				}
			});
		},
		move: function(changeX, changeY) {
			var bottoms = [],
				sides = [],
				oldPositions = this.positions;
			oldPositions.forEach(function(position) {
				var col = position.x + changeX,
					row = position.y + changeY,
					lastBottom = bottoms[col] !== undefined ? bottoms[col] : row;
				lastSide = sides[row] !== undefined ? sides[row] : col;

				bottoms[col] = (row >= lastBottom ? row : lastBottom);
				sides[row] = ((changeX > 0 ? col >= lastSide : col < lastSide) ? col : lastSide);
			});
			var toBottom;
			var toSide;
			if(changeY != 0) {
				for(var col in bottoms) {
					if(col < 0 || col >= 10) {
						continue;
					}
					var row = bottoms[col];
					if(row === blocks.length ||
						row >= 0 && row < blocks.length && blocks[row][col].color > -1) {
						toBottom = true;
						break;
					}
				}
			}
			if(changeX != 0) {
				for(var row in sides) {
					if(row >= blocks.length) {
						continue;
					}
					var col = sides[row];
					if(col < 0 || col >= 10 || row >= 0 && blocks[row][col].color > -1) {
						toSide = true;
						break;
					}
				}
			}
			if(toBottom && toSide) {
				return {
					toBottom: true,
					toSide: true
				};
			} else if(toBottom && changeX === 0) {
				return {
					toBottom: true,
					toSide: false
				};
			} else if(toSide && changeY === 0) {
				return {
					toBottom: false,
					toSide: true
				};
			}
			changeX = toSide ? 0 : changeX;
			changeY = toBottom ? 0 : changeY;

			this.basePosition = {
				x: this.basePosition.x + changeX,
				y: this.basePosition.y + changeY
			}
			this.positions = oldPositions.map(function(position) {
				var x = position.x + changeX,
					y = position.y + changeY;
				return {
					x: x,
					y: y
				};
			});
			this.drew();
			return {
				toBottom: toBottom,
				toSide: toSide
			};
		},
		rotate: function() {
			if(this.type === 0) {
				return;
			}
			var matrixType = rotateMatrix(this.type, this.matrixType);
			var positions = matrix2Positions(this.basePosition, this.type, matrixType);

			var shift = checkRotatePositions(positions, this.positions);
			if(!shift) {
				return;
			} else if(shift.x != 0 || shift.y != 0) {
				positions = positions.map(function(position) {
					return {
						x: position.x + shift.x,
						y: position.y + shift.y
					};
				});
				this.basePosition = {
					x: this.basePosition.x + shift.x,
					y: this.basePosition.y + shift.y
				};
			}
			this.matrixType = matrixType;
			this.positions = positions;
			this.drew();
		},
		flash: function(callBack) {
			for(var i=0;i<this._Positions.length;i++){
				var position = this._Positions[i];
					row = position.y,
					col = position.x;
				if(col < 0 || col >= 10) {
					return;
				}
				if(row < 0 || row >= blocks.length) {
					return;
				}
				var block = blocks[row][col];
				if(block && block.color > -1) {
					if(i === this._Positions.length-1){
						block.el.addEventListener(Animation_End, function(){
							if(callBack){
								setTimeout(callBack, 0);
							}
						});
					}
					toggleClass(block.el, 'flash');
					setTimeout(function() {
						toggleClass(block.el, 'flash')
					}, 500);
				}
			}
		},
		dispose:function(){
			this._Positions.forEach(function(position){
				if(position.y>=0 && position.y<blocks.length){
					var block = blocks[position.y][position.x];
					if(block){
						block.inCurrentShape = false;
					}
				}
				this._Positions = this.oldPositions = null;
			});
		},
		get positions() {
			return this._Positions;
		},
		set positions(newPositions) {
			if(this._Positions) {
				this.oldPositions = this._Positions.filter(function(position) {
					return position.y >= 0 && !newPositions.some(function(oldPosition) {
						return position.x === oldPosition.x && position.y === oldPosition.y;
					});
				})
			}
			this._Positions = newPositions;
		}
	}

	function rotateMatrix(type, matrixType) {
		if(type === 0) {
			return 0;
		}
		if(matrixType === SHAPES[type].length - 1) {
			return 0
		}
		return matrixType + 1;
	}

	function matrix2Positions(basePosition, type, matrixType) {
		var positions = [];
		var matrix = SHAPES[type][matrixType];
		for(var i = 0; i < matrix.length; i++) {
			var sub = matrix[i];
			for(var y = 0; y < sub.length; y++) {
				if(sub[y] === 1) {
					positions.push({
						x: basePosition.x + y,
						y: basePosition.y + i
					});
				}
			}
		}
		return positions;
	}

	function checkRotatePositions(positions) {
		out: for(var s in SHIFTS) {
			var shift = SHIFTS[s];
			for(var i in positions) {
				var row = positions[i].y + shift.y,
					col = positions[i].x + shift.x;
				if(col < 0 || col >= 10) {
					continue out;
				}
				if(row < 0) {
					continue;
				}
				if(row >= blocks.length) {
					continue out;
				}
				var block = blocks[row][col];
				if(block && !block.inCurrentShape && block.color > -1) {
					continue out;
				}
			}
			return shift;
		}
	}

	function toggleClass(el, className) {
		if(el) {
			el.classList.toggle(className);
		}
	}

	if(doc.readyState === 'complete' && doc.body) {
		gameStart();
	} else {
		doc.addEventListener('DOMContentLoaded', gameStart, false);
	}
})(document, window)